﻿using ServerListerner.Enums;
using ServerListerner.Handlers;
using ServerListerner.Helper;
using ServerListerner.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.Json;

namespace ServerListerner.Connections
{
    public class HTTPConnection : IHTTPConnection
    {
        public Action<IHTTPConnection,HTTPRequest> OnReceived { get; set; }
        private Socket _socket;
        private bool IsClosed = false;
        private int _bufferLen = 0;
        private byte[] _buffer=null;
        private int _sendBufferLen = 0;
        private byte[] _sendBuffer = null;
        private List<byte> _received = new List<byte>();
        private readonly Encoding? _encoding = null;
        public EndPoint? RemoteEndPoint { get; private set; }
        public List<ListenPath> _path = new List<ListenPath>();
        private string? Version = string.Empty;
        private Dictionary<string, string> _Header = null;
        private IRouterHandler? _handler;
        

        internal HTTPConnection(Socket socket, Action<IHTTPConnection>? action, List<ListenPath> path, IRouterHandler handler=null, Encoding? encoding=null)
        {
            this._socket = socket;
            this._path.Clear();
            if (action != null)
                this._path = path;
            this._bufferLen = this._socket.ReceiveBufferSize;
            this._sendBufferLen = this._socket.SendBufferSize;
            this._encoding = encoding ?? Encoding.UTF8;
            this.RemoteEndPoint = this._socket.RemoteEndPoint;
            this._handler = handler;
            action?.Invoke(this);
            StartReceived();
        }
        private void Send(byte[] bytes) => this.BeginSend(bytes);
        public void Send(string Message,Dictionary<string,string> Header=null)
        {
            this._Header = this._Header?.AddHeader(Header) ?? new Dictionary<string, string>();
            HTTPResponse response = new HTTPResponse()
            {
                Content = Message,
                Code = HttpStatusCode.OK,
                Headers = this._Header,
                Version = this.Version,
            };
            byte[]? _sendByte = response.HTTPEncoder();
            if (_sendByte == null || _sendByte.Length <= 0)
            {
                this.Close();
                return;
            }
            this.BeginSend(_sendByte);

        }
        public void Dispose() => this.Close();
        private void StartReceived()
        {
            this._buffer = null;
            this._buffer=new byte[this._bufferLen];
            if (!this._socket.Connected) this.Close();
            if (IsClosed) return;
            this._socket.BeginReceive(this._buffer, 0, _bufferLen, SocketFlags.None, Receiveding, null);
        }
        private void Receiveding(IAsyncResult iar)
        {
            if (!this._socket.Connected) this.Close();
            if (IsClosed) return;
            bool success = iar.AsyncWaitHandle.WaitOne(500, false);
            int _readLen = _socket.EndReceive(iar);
            if (_readLen == 0) Close();
            if (IsClosed) return;
            if(this._received==null)
                this._received = new List<byte>();
            this._received.AddRange(this._buffer.Take(_readLen));
            if (this._socket.Available > 0)
                StartReceived();
            if (this._received==null||this._received.Count<=0) return;
            EndReceived(this._received.ToArray());
        }
        private void EndReceived(byte[] buffer)
        {
            try
            {
                this._received.Clear();
                HTTPRequest request = buffer.HTTPDecoder(buffer.Length);
                this.Version = request.Version;
                this._Header = request.Headers?.RemoveHeaderHost() ?? new Dictionary<string, string>();
                var method = this._handler?.Routes.Where(s => s.Path.ToLower() == request.Url.ToLower() && s.RequestMethod?.ToLower() == request.Method.ToLower()).FirstOrDefault();
                if (method != null){
                    object? obj = this._handler?.HandleRequest(method,request);
                    string? resp = obj.ResultData();
                    this.Send(new HTTPResponse()
                    {
                        Version = this.Version,
                        Code = HttpStatusCode.OK,
                        Headers = this._Header,
                        Content = resp
                    }.HTTPEncoder());
                    return;
                }
                if (!this._path.Any(s => s.Method?.ToLower() == request.Method?.ToLower() && s.Path?.ToLower() == request.Url?.ToLower()))
                {
                    this.Send(new HTTPResponse()
                    {
                        Version = request.Version,
                        Code = HttpStatusCode.NotFound,
                        Headers = this._Header,
                        Content = "Not Fount"
                    }.HTTPEncoder());
                    return;
                }
               if(OnReceived!=null)
                    OnReceived?.Invoke(this, request);
               else
                    this.Send(new HTTPResponse()
                    {
                        Version = this.Version,
                        Code = HttpStatusCode.InternalServerError,
                        Headers = this._Header,
                        Content = "Internal Server Error"
                    }.HTTPEncoder());
                return;
            }
            catch (Exception ex)
            {
                //OnError?.Invoke(this, ex);    
                this.Send(new HTTPResponse()
                {
                    Version = this.Version,
                    Code = HttpStatusCode.InternalServerError,
                    Headers = this._Header,
                    Content = "Internal Server Error"
                }.HTTPEncoder());
                return;
            }

        }

        private void BeginSend(byte[] bytes)
        {
            try
            {
                var send = bytes.Take(this._sendBufferLen).ToArray();
                int len = send.Length > this._sendBufferLen ? this._sendBufferLen : send.Length;
                var data = bytes.Skip(len).ToArray();
                this._socket.BeginSend(send, 0, len, SocketFlags.None, SendCallback, data);
                //int size = 0;
                //bool success = true;
                //while (success)
                //{
                //    if (!this._socket.Connected)
                //        this.Close();
                //    if (IsClosed)
                //        return;
                //    IAsyncResult result = this._socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, null, null);
                //    success = result.AsyncWaitHandle.WaitOne(100, true);
                //    if (success&&size<bytes.Length)
                //        size+=this._socket.EndSend(result);
                //    else
                //        success = false;
                //}
            }
            catch (Exception ex)
            {
                this.Send(new HTTPResponse()
                {
                    Version = this.Version,
                    Code = HttpStatusCode.InternalServerError,
                    Headers = this._Header,
                    Content = "Internal Server Error"
                }.HTTPEncoder());
            }
            this.Close();
            if (IsClosed)
                return;


        }
        private void SendCallback(IAsyncResult iar)
        {
            try
            {
                if (this._socket.Connected)
                {
                    byte[] bytes = (byte[])iar.AsyncState;
                    int size = this._socket.EndSend(iar);
                    if (bytes.Length > 0)
                        BeginSend(bytes);
                }
            }
            catch (Exception ex)
            {
                this.Send(new HTTPResponse()
                {
                    Version = this.Version,
                    Code = HttpStatusCode.InternalServerError,
                    Headers = this._Header,
                    Content = "Internal Server Error"
                }.HTTPEncoder());
            }
            this.Close();
            if (IsClosed)
                return;
        }
        

        private void Close()
        { 
            this.IsClosed = true;
            //OnClosed?.Invoke(this);
            this.CloseSocket();
            this.SetNull();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            //GC.Collect();
        }
        private void CloseSocket()
        {
            this._socket?.Close();
            this._socket?.Dispose();
        }
        private void SetNull()
        {
            //this.OnClosed = null;
            //this.OnError = null;
            this.OnReceived = null;
            this._buffer = null;
            this._path = null;
            this.Version = string.Empty;
            this._Header = null;
        }
    }
}
